<html>
<head><meta charset="utf-8"></head>
<body>
	<script type="text/javascript">

        console.log = (...params) => {
            document.write(params.join(" ")+"<br>");
        };

        function reqAsPromise(req) {
            return new Promise((resolve, reject) => {
                req.onsuccess = () => resolve(req);
                req.onerror = (err) => reject(err);
            });
        }

        function txnAsPromise(txn) {
            return new Promise((resolve, reject) => {
                txn.addEventListener("complete", resolve);
                txn.addEventListener("abort", reject);
            });
        }

        function iterateCursor(cursor, processValue) {
            // TODO: does cursor already have a value here??
            return new Promise((resolve, reject) => {
                cursor.onerror = (event) => {
                    reject(new Error("Query failed: " + event.target.errorCode));
                };
                // collect results
                cursor.onsuccess = (event) => {
                    const cursor = event.target.result;
                    if (!cursor) {
                        resolve(false);
                        return; // end of results
                    }
                    const {done, jumpTo} = processValue(cursor.value, cursor.key);
                    if (done) {
                        resolve(true);
                    } else {
                        cursor.continue(jumpTo);
                    }
                };
            });
        }

        /**
         * Checks if a given set of keys exist.
         * Calls `callback(key, found)` for each key in `keys`, in an unspecified order.
         * If the callback returns true, the search is halted and callback won't be called again.
        */
        async function findKeys(target, keys, backwards, callback) {
            const direction = backwards ? "prev" : "next";
            const compareKeys = (a, b) => backwards ? -indexedDB.cmp(a, b) : indexedDB.cmp(a, b);
            const sortedKeys = keys.slice().sort(compareKeys);
            console.log(sortedKeys);
            const firstKey = backwards ? sortedKeys[sortedKeys.length - 1] : sortedKeys[0];
            const lastKey = backwards ? sortedKeys[0] : sortedKeys[sortedKeys.length - 1];
            const cursor = target.openKeyCursor(IDBKeyRange.bound(firstKey, lastKey), direction);
            let i = 0;
            let consumerDone = false;
            await iterateCursor(cursor, (value, key) => {
                // while key is larger than next key, advance and report false
                while(i < sortedKeys.length && compareKeys(sortedKeys[i], key) < 0 && !consumerDone) {
                    console.log("before match", sortedKeys[i]);
                    consumerDone = callback(sortedKeys[i], false);
                    ++i;
                }
                if (i < sortedKeys.length && compareKeys(sortedKeys[i], key) === 0 && !consumerDone) {
                    console.log("match", sortedKeys[i]);
                    consumerDone = callback(sortedKeys[i], true);
                    ++i;
                }
                const done = consumerDone || i >= sortedKeys.length;
                const jumpTo = !done && sortedKeys[i];
                return {done, jumpTo};
            });
            // report null for keys we didn't to at the end
            while (!consumerDone && i < sortedKeys.length) {
                console.log("afterwards", sortedKeys[i]);
                consumerDone = callback(sortedKeys[i], false);
                ++i;
            }
        }

        async function findFirstOrLastOccurringEventId(target, roomId, eventIds, findLast = false) {
            const keys = eventIds.map(eventId => [roomId, eventId]);
            const results = new Array(keys.length);
            let firstFoundEventId;

            // find first result that is found and has no undefined results before it
            function firstFoundAndPrecedingResolved() {
                let inc = findLast ? -1 : 1;
                let start = findLast ? results.length - 1 : 0;
                for(let i = start; i >= 0 && i < results.length; i += inc) {
                    if (results[i] === undefined) {
                        return;
                    } else if(results[i] === true) {
                        return keys[i];
                    }
                }
            }

            await findKeys(target, keys, findLast, (key, found) => {
                const index = keys.indexOf(key);
                results[index] = found;
                firstFoundEventId = firstFoundAndPrecedingResolved();
                return !!firstFoundEventId;
            });

            return firstFoundEventId;
        }

        (async () => {
            let db;
            let isNew = false;
            // open db
            {
                const req = window.indexedDB.open("prototype-idb-continue-key");
                req.onupgradeneeded = (ev) => {
                    const db = ev.target.result;
                    db.createObjectStore("timeline", {keyPath: ["roomId", "eventId"]});
                    isNew = true;
                };
                db = (await reqAsPromise(req)).result;
            }
            const roomId = "!abcdef:localhost";
            if (isNew) {
                const txn = db.transaction(["timeline"], "readwrite");
                const store = txn.objectStore("timeline");
                for (var i = 1; i <= 100; ++i) {
                    store.add({roomId, eventId: `$${i * 10}`});
                }
                await txnAsPromise(txn);
            }
            console.log("show all in order we get them");
            {
                const txn = db.transaction(["timeline"], "readonly");
                const store = txn.objectStore("timeline");
                const cursor = store.openKeyCursor();
                await iterateCursor(cursor, (value, key) => {
                    console.log(key);
                    return {done: false};
                });
            }
            console.log("run findKeys");
            {
                const txn = db.transaction(["timeline"], "readonly");
                const store = txn.objectStore("timeline");
                const eventIds = ["$992", "$1000", "$1010", "$991", "$500", "$990"];
                // const eventIds = ["$992", "$1010"];
                const keys = eventIds.map(eventId => [roomId, eventId]);
                await findKeys(store, keys, false, (key, found) => {
                    console.log(key, found);
                });
            }
            console.log("run findFirstOrLastOccurringEventId");
            {
                const txn = db.transaction(["timeline"], "readonly");
                const store = txn.objectStore("timeline");
                const eventIds = ["$992", "$1000", "$1010", "$991", "$500", "$990", "$123"];
                const firstMatch = await findFirstOrLastOccurringEventId(store, roomId, eventIds, false);
                console.log("firstMatch", firstMatch);
                const lastMatch = await findFirstOrLastOccurringEventId(store, roomId, eventIds, true);
                console.log("lastMatch", lastMatch);
            }
        })();
	</script>
</body>
</html>
